/**
 * @file 触摸装饰器
 * @author DuskyHuang 圣子
*/

import { Component, EventTouch, Node } from "cc";
import { isComponent } from "../cc/object";
import { swapper } from "./swapper";
const { EventType: NE } = Node;

/** 触摸器接口 */
export interface ITouchable {
    touchable: boolean;
    onTouchStart?(arg: EventTouch): void;
    onTouchMove?(arg: EventTouch): void;
    onTouchEnd?: (arg: EventTouch, out: boolean) => void;
}

/** 触摸类装饰器 */
export const toucher: DecoratorSwapper<ClassDecorator, [
    capture?: boolean,  // 注册在捕获阶段（容器监听先于内容）
    swallow?: boolean,  // 吞噬节点传递（捕获阶段屏蔽内容，冒泡阶段屏蔽容器）
    through?: boolean   // 启用事件透传（同级节点均可监听）
]> = swapper<ClassDecorator>(([ctor]: [ConcreteClass<ITouchable>],
    capture?: boolean, swallow?: boolean, through?: boolean) => {
    const _ = ctor.prototype;
    if (isComponent(ctor)) {
        const { onEnable, onDisable } = _;
        Object.defineProperties(_, {
            onEnable: {
                configurable: true, enumerable: false,
                value: function (this: ITouchable) {
                    this[$SYM.SWITCH](false);
                    Function.invoke(onEnable, this);
                }
            },
            onDisable: {
                configurable: true, enumerable: false,
                value: function (this: Component & ITouchable) {
                    this.touchable = false;
                    Function.invoke(onDisable, this);
                }
            }
        });
        Object.defineProperties(_, {
            [$SYM.TOUCHABLE]: { configurable: true, enumerable: false, writable: true, value: true },
            touchable: {
                configurable: true, enumerable: false,
                set: function (this: Component, arg: boolean) {
                    const _touchable = this[$SYM.TOUCHABLE];
                    if (_touchable === arg) return;
                    this[$SYM.TOUCHABLE] = arg;
                    if (arg) {
                        this[$SYM.SWITCH](false);
                    } else {
                        this.node.off(NE.TOUCH_START, this[$SYM.ON_START], this, capture);
                        this.node.off(NE.TOUCH_END, this[$SYM.ON_END], this, capture);
                        this.node.off(NE.TOUCH_CANCEL, this[$SYM.ON_CANCEL], this, capture);
                        this.node.off(NE.TOUCH_MOVE, this[$SYM.ON_MOVE], this, capture);
                    }
                },
                get: function (this: Component) {
                    return this[$SYM.TOUCHABLE];
                },
            },
            [$SYM.SWITCH]: {
                configurable: true, enumerable: false,
                value: function (this: Component, load: boolean) {
                    if (!this[$SYM.TOUCHABLE]) return;
                    if (!load) {
                        this.node.on(NE.TOUCH_START, this[$SYM.ON_START], this, capture);
                        this.node.off(NE.TOUCH_END, this[$SYM.ON_END], this, capture);
                        this.node.off(NE.TOUCH_CANCEL, this[$SYM.ON_CANCEL], this, capture);
                        this.node.off(NE.TOUCH_MOVE, this[$SYM.ON_MOVE], this, capture);
                    } else {
                        this.node.on(NE.TOUCH_END, this[$SYM.ON_END], this, capture);
                        this.node.on(NE.TOUCH_CANCEL, this[$SYM.ON_CANCEL], this, capture);
                        this.node.on(NE.TOUCH_MOVE, this[$SYM.ON_MOVE], this, capture);
                        this.node.off(NE.TOUCH_START, this[$SYM.ON_START], this, capture);
                    }
                },
            },
            [$SYM.ON_START]: {
                configurable: true, enumerable: false,
                value: function (this: ITouchable, evt: EventTouch) {
                    if (swallow) evt.propagationStopped = true;
                    if (through) evt.preventSwallow = true;
                    this[$SYM.SWITCH](true);
                    Function.invoke(this.onTouchStart, this, evt);
                }
            },
            [$SYM.ON_MOVE]: {
                configurable: true, enumerable: false,
                value: function (this: ITouchable, evt: EventTouch) {
                    if (swallow) evt.propagationStopped = true;
                    if (through) evt.preventSwallow = true;
                    Function.invoke(this.onTouchMove, this, evt);
                }
            },
            [$SYM.ON_END]: {
                configurable: true, enumerable: false,
                value: function (this: ITouchable, evt: EventTouch) {
                    if (swallow) evt.propagationStopped = true;
                    if (through) evt.preventSwallow = true;
                    this[$SYM.SWITCH](false);
                    Function.invoke(this.onTouchEnd, this, evt, false);
                }
            },
            [$SYM.ON_CANCEL]: {
                configurable: true, enumerable: false,
                value: function (this: ITouchable, evt: EventTouch) {
                    if (swallow) evt.propagationStopped = true;
                    if (through) evt.preventSwallow = true;
                    this[$SYM.SWITCH](false);
                    Function.invoke(this.onTouchEnd, this, evt, true);
                }
            },
        });
        return ctor;
    } else console.warn('@toucher must decorated on cc.Component');
});

const $SYM = {
    SWITCH: Symbol('SET_TOUCH'),
    TOUCHABLE: Symbol('TOUCHABLE'),
    ON_START: Symbol('ON_START'),
    ON_MOVE: Symbol('ON_MOVE'),
    ON_END: Symbol('ON_END'),
    ON_CANCEL: Symbol('ON_CANCEL'),
}